"""Opinionated wrapper for a KCIDB file."""
import copy
import json
import pathlib

from cki_lib.kcidb.validate import PRODUCER_KCIDB_SCHEMA
from cki_lib.kcidb.validate import validate_extended_kcidb_schema
from cki_lib.logger import get_logger

LOGGER = get_logger(__name__)


class ObjectNotFound(Exception):
    """Object is not present in data."""


class KCIDBFile:
    """Opinionated wrapper for a KCIDB file.

    Designed to make common cki-project tasks convenient, but not necessarily support more general
    KCIDB use cases
    """

    DEFAULT_CONTENT = {'version': {'major': PRODUCER_KCIDB_SCHEMA.major,
                                   'minor': PRODUCER_KCIDB_SCHEMA.minor}}

    def __init__(self, filename, validate=True):
        """Open and validate file."""
        self.validate = validate
        self.file_path = pathlib.Path(filename)
        if self.file_path.exists():
            self.data = json.loads(self.file_path.read_text(encoding='utf8'))
            self.validate_if_needed()
        else:
            self.data = copy.deepcopy(self.DEFAULT_CONTENT)

    def _get_obj(self, key, obj_id):
        """
        Return object matching id.

        If obj_id=None, assume it's expecting an object for the key
        in the kcidb file.
        """
        objs = self.data.get(key, [])

        if not obj_id:
            if not objs:
                self.data[key] = [{}]
            elif len(objs) > 1:
                raise LookupError(f'KCIDB file has more than one item in {key}')
            return self.data[key][0]

        for obj in objs:
            if obj['id'] == obj_id:
                return obj

        raise ObjectNotFound(f'{key} id {obj_id} does not exist')

    def _set_obj(self, key, obj_id, value):
        """Set or update object."""
        try:
            obj = self._get_obj(key, obj_id)
        except ObjectNotFound:
            self.data.setdefault(key, [])
            self.data[key].append(value)
        else:
            index = self.data[key].index(obj)
            self.data[key][index] = value

    @property
    def checkout(self):
        """Check for at most one checkout and return it."""
        return self._get_obj('checkouts', None)

    @checkout.setter
    def checkout(self, value):
        """Check for at most one build and set it."""
        return self._set_obj('checkouts', None, value)

    def get_checkout(self, checkout_id):
        """Get checkout with id=checkout_id."""
        return self._get_obj('checkouts', checkout_id)

    def set_checkout(self, checkout_id, checkout):
        """Get checkout with id=checkout_id."""
        return self._set_obj('checkouts', checkout_id, checkout)

    @property
    def build(self):
        """Check for at most one build and return it."""
        return self._get_obj('builds', None)

    @build.setter
    def build(self, value):
        """Check for at most one build and set it."""
        return self._set_obj('builds', None, value)

    def get_build(self, build_id):
        """Get build with id=build_id."""
        return self._get_obj('builds', build_id)

    def set_build(self, build_id, build):
        """Get build with id=build_id."""
        return self._set_obj('builds', build_id, build)

    def get_test(self, test_id):
        """Get test with id=test_id."""
        return self._get_obj('tests', test_id)

    def set_test(self, test_id, test):
        """Set test with id=test_id."""
        return self._set_obj('tests', test_id, test)

    def validate_if_needed(self):
        """Check if self.data contains valid KCIDB data."""
        if self.validate:
            validate_extended_kcidb_schema(self.data, raise_for_cki=False)

    def save(self):
        """Validate and save data to file."""
        # NOTE: upgrade will fail if data has no version,
        # and it will try to validate if KCIDB_IO_HEAVY_ASSERTS is defined
        PRODUCER_KCIDB_SCHEMA.upgrade(self.data, copy=False)

        self.validate_if_needed()
        self.file_path.write_text(json.dumps(self.data, indent=4), encoding='utf8')
